﻿using PhiStore.ObjectManaging;
using PhiStore.Packages;
using PhiStore.Packages.InternalPackages;

namespace PhiStore.Communication
{
    partial class Worker
    {
        //Making a copy of an object (e.g. replication)
        private void CreateCopy(ObjectCopy package)
        {
            currentObjectName = package.Key;
            ReturnCode result = LockEntry(); //obtaining lock

            if (result == ReturnCode.Success)
            {
                ObjectLock oe = objectLock;

                result |= oe.Device.UpdateObjectInfo(package.Key, package.ObjectInformation, oe.Policy.EnforceAtomicity);

                oe.ObjectInformation = package.ObjectInformation;

                for (long i = 0; i < oe.ObjectInformation.Count; i++)
                {
                    long offset = package.ObjectInformation.FirstIndex;
                    long index = offset + i;
                    if(oe.Device.Exist(package.Key, index))
                    {
                        result |= oe.Device.UpdateObject(package.Key, index, package.Objects[(int)i], oe.Policy.EnforceAtomicity);
                    }
                    else
                    {
                        result |= oe.Device.CreateObject(package.Key, index, package.Objects[(int)i], oe.Policy.EnforceAtomicity);
                    }
                }

                if (!oe.Policy.EnforceAtomicity) //no need to let the object remain locked
                    ReleaseLockEntry();
                else
                    locks.SaveLock(package.Key, oe);
            }

            SendReply(result);
        }
   
        //append / update during replication
        private void UpdateCopy(UpdateCopy package)
        {
            currentObjectName = package.Key;
            ReturnCode result = LockEntry(); //obtaining lock

            if (result == ReturnCode.Success)
            {
                ObjectLock oe = objectLock;

                for (long i = 0; i < package.Objects.Count; i++)
                {
                    byte[] data = package.Objects[(int)i];
                    long index = package.Offset + oe.ObjectInformation.FirstIndex + i;

                    if (oe.Device.Exist(package.Key, index))
                    {
                        result |= oe.Device.UpdateObject(package.Key, index, data,
                            oe.Policy.EnforceAtomicity
                            );
                    }
                    else
                    {
                        result |= oe.Device.CreateObject(package.Key, index, data, 
                            oe.Policy.EnforceAtomicity
                            );
                    }
                }

                oe.Device.UpdateObjectInfo(package.Key, package.ObjectInformation, oe.Policy.EnforceAtomicity);
                oe.ObjectInformation = package.ObjectInformation;

                if (!oe.Policy.EnforceAtomicity) //no need to let the object remain locked
                    ReleaseLockEntry();
                else
                    locks.SaveLock(package.Key, oe);
            }

            SendReply(result);
        }

        //delete stuff
        private void DeleteEntriesForwarded(ListDeleteCopy package)
        {
            currentObjectName = package.Key;
            ReturnCode result = LockEntry(); //obtaining lock
            if (result == ReturnCode.Success)
            {
                ObjectLock oe = objectLock;
                long startDeletion = oe.ObjectInformation.FirstIndex + package.Offset;

                //Asserting that the versions are identical

                for (long i = 0; i < package.Count; i++)
                {
                    long index = i + startDeletion;
                    oe.Device.DeleteObject(package.Key, index, oe.Policy.EnforceAtomicity);
                }

                long leftPortionSize = package.Offset;
                long rightPortionSize = oe.ObjectInformation.Count - package.Count - package.Offset;

                if (leftPortionSize < rightPortionSize)
                {   //first half is smallest
                    for (long index = startDeletion - 1; index >= oe.ObjectInformation.FirstIndex; index--)
                    {
                        long newIndex = index + package.Count;
                        oe.Device.Rename(package.Key, index, newIndex, oe.Policy.EnforceAtomicity);
                    }
                }
                else
                { //second half is smallest
                    for (long i = 0; i < oe.ObjectInformation.Count - package.Offset - package.Count; i++)
                    {
                        long oldIndex = startDeletion + i + package.Count;
                        long newIndex = startDeletion + i;
                        oe.Device.Rename(package.Key, oldIndex, newIndex, oe.Policy.EnforceAtomicity);
                    }
                }

                result |= oe.Device.UpdateObjectInfo(package.Key, package.ObjectInformation, oe.Policy.EnforceAtomicity);

                oe.ObjectInformation = package.ObjectInformation;

                if (!oe.Policy.EnforceAtomicity) //no need to let the object remain locked
                    ReleaseLockEntry();
                else
                    locks.SaveLock(package.Key, oe);
            }

            SendReply(result);
        }

        private void DeleteCopy(DeleteCopy package)
        {
            currentObjectName = package.Key;
            ReturnCode result = LockEntry();

            if (result == ReturnCode.Success)
            {
                ObjectLock oe = objectLock;
                result |= oe.Device.DeleteObject(package.Key, oe.Policy.EnforceAtomicity);

                if (!oe.Policy.EnforceAtomicity)//no need to have lock on object
                {
                    PhiServer.Instance.ObjectManagerChannel.Send(new RemoveObject(package.Key, fromObjectManager));
                    result |= (ReturnCode)fromObjectManager.Receive();
                }
           
                if (!oe.Policy.EnforceAtomicity) //no need to let the object remain locked
                    ReleaseLockEntry();
                else
                    locks.SaveLock(package.Key, oe);
            }
            SendReply(result);
            
        }

        private void GetData(GetData package)
        {
            currentObjectName = package.Key;
            ReturnCode lockResult = LockEntry(); //obtaining lock

            if (lockResult == ReturnCode.Success)
            {
                ObjectLock oe = objectLock;

                //NO CHECK FOR COORDINATOR
                byte[] obj = oe.Device.ReadObject(package.Key, package.Index);

                ReadReply result = new ReadReply(lockResult, obj);
                ReleaseLockEntry(); //releasing lock
                SendReply(result);
            }
            else
            {
                SendReply(new ReadReply(lockResult, new object()));
            }
        }
    }
}
